/*
This file is a textobot App Text Capture plugin.
*/

// ensure our module is loaded
const libtextobot = Module.load('/Library/MobileSubstrate/DynamicLibraries/textobot.dylib');
function TB_api(name, rettype, argstype) {
    return new NativeFunction(libtextobot.getExportByName(name), rettype, argstype);
}

// pickup textobot APIs
const textobot = {
    TB_logpath      : TB_api('TB_logpath', 'pointer', []),
    TB_logbuff      : TB_api('TB_logbuff', 'pointer', []),
    TB_logflush     : TB_api('TB_logflush', 'int', []),
    TB_dialog       : TB_api('TB_dialog','int', ['pointer']),
    TB_system       : TB_api('TB_system','int', ['pointer']),
    TB_root_system  : TB_api('TB_root_system', 'int', ['pointer']),
    TB_window       : TB_api('TB_window', 'pointer', []),
    TB_touch        : TB_api('TB_touch', 'int', ['int', 'int']),
    TB_swipe        : TB_api('TB_swipe', 'int', ['int', 'int', 'int', 'int']),
    // API likes TB_*_new should call TB_sptr_free to free the heap memory
    // see free function bellow
    TB_appdir_new   : TB_api('TB_appdir_new', 'pointer', []),
    TB_docdir_new   : TB_api('TB_docdir_new', 'pointer', []),
    TB_tmpdir_new   : TB_api('TB_tmpdir_new', 'pointer', []),
    TB_urlget_new   : TB_api('TB_urlget_new', 'pointer', ['pointer']),
    TB_urlpost_new  : TB_api('TB_urlpost_new', 'pointer', ['pointer', 'pointer']),
    TB_sptr_free    : TB_api('TB_sptr_free', 'void', ['pointer']),
    // ...
    // see https://gitee.com/geekneo/Textobot/blob/master/Doc/api.md for more APIs
};

// put the string to textobot's native buffer
function puts(s) {
    // logbuff's size is 4096
    if (s.length >= 4096) {
        console.log('Ignoring long string ' + s.length);
        return
    }
    textobot.TB_logbuff().writeUtf8String(s);
    return textobot.TB_logbuff();
}

// wrap logger util
function logs(s) {
    puts(s);
    const ret = textobot.TB_logflush();
    if (ret != 0) {
        console.log(ret + ' : ' + s);
    }
}

// wrap free util
function free(ptr) {
    var sptr = puts(ptr.toString(16))
    textobot.TB_sptr_free(sptr);
}

// slow logger, usually for global log
console.log("The URLCapture plugin's log file is at " + 
    textobot.TB_logpath().readUtf8String());

// fast log, usually for hooker with better performance
logs('The URLCapture plugin is running ...');

function backtrace(context) {
    // turn to 0 if you want to dump the callstack backtrace
    if (1) {
        return 'Off';
    }
    return Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n');
}

// tracer for -[NSURL initWithString:]
function url_capture_initWithString(args) {
    var inst = new ObjC.Object(args[2]);
    logs(inst.UTF8String() + '\ninitWithString\n' + backtrace(this.context) + '\n');
}

// tracer for CFURLCreateWithString
function url_capture_CFURLCreateWithString(args) {
    var inst = new ObjC.Object(args[1]);
    logs(inst.UTF8String() + '\nCFURLCreateWithString\n' + backtrace(this.context) + '\n');
}

// tracer for CFURLCreateWithBytes
function url_capture_CFURLCreateWithBytes(args) {
    var url = args[1].readCString(args[2].toInt32());
    logs(url + '\nCFURLCreateWithBytes\n' + backtrace(this.context) + '\n');
}

function url_capture_impl() {
    // add interceptor for -[NSURL initWithString:]
    if (1) {
        const NSURL = ObjC.classes.NSURL;
        Interceptor.attach(NSURL['- initWithString:'].implementation, {
            onEnter: url_capture_initWithString
        });
    }
    // add interceptor for CFURLCreateWithString
    if (1) {
        const CFURLCreateWithString = Module.getExportByName('CoreFoundation', 'CFURLCreateWithString');
        Interceptor.attach(CFURLCreateWithString, {
            onEnter: url_capture_CFURLCreateWithString
        });
    }
    // add interceptor for CFURLCreateWithBytes
    if (1) {
        const CFURLCreateWithBytes = Module.getExportByName('CoreFoundation', 'CFURLCreateWithBytes');
        Interceptor.attach(CFURLCreateWithBytes, {
            onEnter: url_capture_CFURLCreateWithBytes
        });
    }
}

if (1) {
    url_capture_impl();
}
